﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil.Cil;
using Mono.Cecil;
using System.Collections;
using devtm.Aop;
using devtm.Aop.Extensions;

namespace devtm.Aop.Copy
{
    

    /// <summary>
    /// Prends en charge la copie du contenu d'une method.
    /// dans le cas ou des methods interne aux type de source, une translation est faite avec sa corresondance dans la method en cours.
    /// </summary>
    public class CopyMethodBody
    {

        private Hashtable Variables = new Hashtable();
        private Hashtable Fields;
        private Hashtable Parameters = new Hashtable();
        private Hashtable Instructions = new Hashtable();
        private MethodDefinition methodSource;
        private MethodDefinition methodTarget;
        private ModuleDefinition _module;
        private TypeDefinition TargetType;

        public CopyMethodBody(MethodDefinition methodSource, MethodDefinition methodTarget, Hashtable fields)
        {
            this.methodSource = methodSource;
            this.methodTarget = methodTarget;
            this.Fields = fields;
            _module = methodTarget.Module;
            TargetType = methodTarget.DeclaringType;
        }

        public void CopyReferenceInstructionFromMethodBody()
        {

            //on copie les instructions
            var instructions = methodSource.Body.Instructions.ToList();

            DuplicateVariables();
            DuplicateParameters();

            ModuleDefinition _module = methodTarget.Module;

            foreach (Instruction item6 in instructions)
            {
                Instruction i = Clone(item6);
                methodTarget.Body.Instructions.Add(i);
            }

        }

        private Instruction Clone(Instruction instruction)
        {

            if (Instructions.ContainsKey(instruction))
                return Instructions[instruction] as Instruction;

            Instruction i2 = null;

            OpCode opcode = instruction.OpCode;
            object operand = instruction.Operand;

            #region Value Type
            
            if (operand == null)
                i2 = Instruction.Create(opcode);
            else if (operand is byte)
                i2 = Instruction.Create(opcode, (byte)operand);
            else if (operand is double)
                i2 = Instruction.Create(opcode, (double)operand);
            else if (operand is float)
                i2 = Instruction.Create(opcode, (float)operand);
            else if (operand is int)
                i2 = Instruction.Create(opcode, (int)operand);
            else if (operand is long)
                i2 = Instruction.Create(opcode, (long)operand);
            else if (operand is sbyte)
                i2 = Instruction.Create(opcode, (sbyte)operand);
            else if (operand is string)
                i2 = Instruction.Create(opcode, operand as string);

            #endregion

            else if (operand is ParameterDefinition)
                return Instruction.Create(opcode, Parameters[operand as ParameterDefinition] as ParameterDefinition);
            else if (operand is VariableDefinition)
                i2 = Instruction.Create(opcode, (Variables[operand] as VariableDefinition));
            else if (operand is MethodReference)
            {

                TypeReference t1 = (operand as MethodReference).DeclaringType;

                var n1 = t1.ToString();
                var n2 = methodSource.DeclaringType.ToString();
                var MethodDef = (operand as MethodReference).Resolve();                   
             
                if (n1 == n2 && !MethodDef.IsStatic)
                {

                    var m2 = TargetType.AsMethod(MethodDef);
                    // Translater avec une method Interne au target type
                    if (m2 == null)
                        throw new Exception("the method {0} can't find in the target type with the same parameters");
                    i2 = Instruction.Create(opcode, _module.Import(m2));

                }
                else
                {
                    i2 = Instruction.Create(opcode, _module.Import(operand as MethodReference));
                }

            }
            else if (operand is FieldReference)
                i2 = Instruction.Create(opcode, (Fields[operand] as FieldReference));
            else if (operand is TypeReference)
                i2 = Instruction.Create(opcode, _module.Import(operand as TypeReference));
            else if (operand is Instruction)
                i2 = Instruction.Create(opcode, GetInstruction(operand as Instruction));

            else
                throw new NotImplementedException("Operand copy");

            /*
            Instruction.Create(opcode, operand as Instruction[]);
            Instruction.Create(opcode, operand as CallSite);
            */

            instruction.SequencePoint.CopyTo(i2);
            Instructions.Add(instruction, i2);
            return i2;

        }

        private Instruction GetInstruction(Instruction instruction)
        {

            if (Instructions.ContainsKey(instruction))
                return Instructions[instruction] as Instruction;

            Instruction i = Clone(instruction);
          
            return i;

        }

        private void DuplicateVariables()
        {

            Variables.Clear();

            // Les variables
            foreach (VariableDefinition v1 in methodSource.Body.Variables)
            {
                var v2 = v1.Clone(methodSource.DeclaringType, methodTarget.DeclaringType);
                Variables.Add(v1, v2);
                methodTarget.Body.Variables.Add(v2);

            }
        }

        private void DuplicateParameters()
        {

            Parameters.Clear();

            foreach (ParameterDefinition p in methodSource.Parameters)
                Parameters.Add(p, methodTarget.Parameters[Parameters.Count]);

        }


    }

}
